package net.cyndeline.componentSystem.gameplay

import language.postfixOps
import net.cyndeline.entitySystem.AbstractEventHandlerSystem
import net.cyndeline.entitySystem.defaultImpl.Aspect
import net.cyndeline.components.gameplay.ActionsLeftComponent
import net.cyndeline.events.gameplay.BeginTurnEvent
import net.cyndeline.entitySystem.EntityRef
import net.cyndeline.entitySystem.Event
import com.escalatesoft.subcut.inject.BindingModule
import com.escalatesoft.subcut.inject.Injectable
import net.cyndeline.events.gameplay.TakeActionEvent
import net.cyndeline.events.gameplay.TakeAction
import net.cyndeline.events.gameplay.ActionComplete
import net.cyndeline.events.gameplay.ActionCompleteEvent
import net.cyndeline.events.gameplay.RetakeActionEvent
import net.cyndeline.events.gameplay.EndTurnEvent
import net.cyndeline.events.gameplay.EndTurn
import net.cyndeline.entitySystem.EntityManager
import net.cyndeline.Global
import net.cyndeline.events.gameplay.BeginTurn
import net.cyndeline.components.gameplay.ActionsLeft
import net.cyndeline.events.gameplay.RetakeAction
import net.cyndeline.components.agent.PlayerInput
import net.cyndeline.components.agent.AI
import net.cyndeline.components.world.TurnStorage

/**
 * Manages the position of units in the turn queue based on whether they have
 * actions left to perform, and informs them when to take an action. Also
 * adjusts the number of actions a unit has left.
 */
class TurnSystem(implicit val bindingModule: BindingModule) 
	extends AbstractEventHandlerSystem(
      new Aspect().getAll(classOf[ActionsLeft]).getSome(classOf[PlayerInput], classOf[AI]), 
      classOf[BeginTurn], classOf[ActionComplete], classOf[RetakeAction]) 
	with Injectable  {
  
  override def onEvent(event: Event, entity: EntityRef) {
    val actions = entity getComponent(classOf[ActionsLeft]) get
    val takeAction = injectOptional[TakeActionEvent] getOrElse { new TakeAction() }
    
    event match {
      case begin: BeginTurn => {
        
        /* Restore the base amount of actions... */
        actions.adjustActionsLeftBy(actions.actionsPerTurn)
          
        if (actions.actionsLeft > 0) {
          
          /* ... and subtract 1 for this action. */
    	  actions.adjustActionsLeftBy(-1)
          entity.send(takeAction)
          
        } else {
          endTurn(entity)
        }
      }
      
      
      
      case successfulActionTaken: ActionComplete => if (actions.actionsLeft > 0) {
    	  actions.adjustActionsLeftBy(-1)
          entity.send(takeAction)
          
        } else {
          endTurn(entity)
        }
      
      case retakeAction: RetakeAction => {
        entity.send(takeAction)
      }
    }
  }
  
  private def endTurn(entity: EntityRef) {
    val endTurn = injectOptional[EndTurnEvent] getOrElse { new EndTurn() }
    entity.send(endTurn)
    processQueue(entity)
  }
  
  /**
   * Puts the current unit at the front of the queue to the back, and begins
   * the turn for the next unit.
   * 
   * @param currentFront The unit that is currently at the front and should be
   * 					 sent to the back.
   */
  private def processQueue(currentFront: EntityRef) {
    val manager = injectOptional[EntityManager] getOrElse { throw new Error("No entity manager specified.") }
    val global = manager.keyEntity(Global)
    
    if (global.isEmpty) {
      throw new Error("No global state object present.")
    }
    
    val queueComponent = global.get.getComponent(classOf[TurnStorage])
    if (queueComponent.isEmpty) {
      throw new Error("No turn queue component present.")
    }
    
    val component = queueComponent.get
    val turnHandler = component.turnHandler
    if (!turnHandler.isEmpty && turnHandler.current != currentFront) {
      throw new Error("The unit currently at the front of the turn queue is not the one that just performed its actions.")
      
    } else if(!turnHandler.isEmpty) {
      val newTurn = injectOptional[BeginTurnEvent] getOrElse { new BeginTurn() }
      
      /* Send the front to the back */
      val next = turnHandler.next()
      
      /* Tell the next entity that it's their turn. */
      next.send(newTurn)
    }
    
  }
}